package cn.buddha.jesj.thread.executor;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.concurrent.*;

/**
 * @author shihao
 * @description 线程池测试
 */
@Slf4j
public class ExecutorTest {

    public static void main(String[] args) {
        List<String> calcResList = new ArrayList<>();
        executor(calcResList);

        double sum = calcResList.stream().mapToDouble(Double::valueOf).sum();
        log.info("计算结果总和：{}", sum);
    }

    public static void executor(List<String> calcResList) {
        // 1- 通过 JDK 的 SPI 方式获取 CalcInterface 接口的实现类
        ServiceLoader<CalcInterface> calcImpls = ServiceLoader.load(CalcInterface.class);

        // 2- 创建一个线程池
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
                .setNameFormat("sh-pool-%d").build();
        ExecutorService threadPool = new ThreadPoolExecutor(3, 6,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

        try {
            // 在并发执行任务时，哪个先执行完就把结果放到 ecs 内部的队列中。
            ExecutorCompletionService<String> ecs = new ExecutorCompletionService<>(threadPool);
            // 用来存放并发任务的 future
            List<Future<String>> futureList = new ArrayList<>();

            // 并发执行 CalcInterface 的实现类
            for (CalcInterface calcImpl : calcImpls) {
                futureList.add(ecs.submit(() -> {
                    Thread.currentThread().setName(calcImpl.getClass().getSimpleName());

                    return calcImpl.calc("呵呵");
                }));
            }

            // 存放各个并发任务的执行结果
            Exception exception = null;
            // 3- 循环获取各个并发任务的执行结果
            for (Future<String> future : futureList) {
                try {
                    /** 这里获取其实就是从 {@link ExecutorCompletionService#completionQueue} 阻塞队列中取值 */
                    calcResList.add(ecs.take().get());
                } catch (InterruptedException | ExecutionException e) {
                    exception = e;
                    break;
                }
            }

            // 4- 如果在获取并发执行结果时，只要其中一个出了异常，那么其他的并发任务也就没有继续执行的必要了，目的就是为了节省资源。
            /**
             * 解释说明，CalcInterface 的实现类总共有三个：CalcHa（耗时 1 秒）、CalcHe（耗时 3 秒）、CalcHei（耗时 5 秒）。
             *
             * 其中 CalcHe（耗时 3 秒）可能会抛出异常，当 CalcHe 抛出异常时，理论上来讲 CalcHei 还没执行完成。
             * 此时，既然 CalcHe 抛出异常了，那么 CalcHei 也没有继续执行下去的必要了。
             *
             * 因为我们这里需要的是 3个实现类全部正常执行完成的结果，无论其中那个执行异常，其他还正在执行的任务也就完全没必要继续执行了，继续执行下去无非就是在浪费资源了。
             */
            if (Objects.nonNull(exception)) {
                log.error("并发执行异常了，取消还正在执行的其他任务，降低资源消耗", exception);
                // 可以把下面这一行注释掉再测一下，就会发现 CalcHei 也会执行完毕。
                futureList.forEach(future -> future.cancel(true));
                throw new RuntimeException("Calc exception");
            }
        } finally {
            threadPool.shutdown();
        }
    }

}
